package com.infolab.aims.web;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jxl.Sheet;
import jxl.Workbook;
import jxl.WorkbookSettings;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.export.JExcelApiExporter;
import net.sf.jasperreports.engine.export.JRXlsExporterParameter;
import net.sf.json.JSONArray;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import com.infolab.aims.GlobalConstant;
import com.infolab.aims.common.AbstractController;
import com.infolab.aims.common.CommonUtil;
import com.infolab.aims.common.bean.ContactInfo;
import com.infolab.aims.common.bean.SearchCriteria;
import com.infolab.aims.common.bean.SearchResult;
import com.infolab.aims.common.exception.DataStoreException;
import com.infolab.aims.common.exception.ResourceNotFoundException;
import com.infolab.aims.domain.Department;
import com.infolab.aims.domain.Section;
import com.infolab.aims.domain.SystemRole;
import com.infolab.aims.domain.SystemUser;
import com.infolab.aims.service.intf.SystemUserService;
import com.infolab.aims.web.form.SystemUserForm;


@Controller
@RequestMapping("/user")
@SessionAttributes(value=UserController.SESSION_SEARCH_CRITERIA)
public class UserController extends AbstractController {

	public static final String SESSION_SEARCH_CRITERIA ="searchCriteria";
	public static final String CONTEXT_PATH ="/department";
	public static final String JSP_MAIN_FORM="userDetailForm";
	public static final String JSP_AD_HOC_FORM="userAdHocForm";
	public static final String JSP_USER_ROLE="userDetailRole";
	public static final String JSP_USER_PASSWORD="userDetailPassword";
	public static final String JSP_MAIN_SEARCH="userSearch";
	
	Log logger = LogFactory.getLog(getClass());

	@Autowired
	SystemUserService systemUserService;

	@RequestMapping(value="create", method = RequestMethod.GET)
	public String createUser(Model model) {
		
		SystemUserForm systemUserForm = new SystemUserForm();
		systemUserForm.setFormMode(GlobalConstant.FORM_MODE_CREATE);
		model.addAttribute("systemUserForm", systemUserForm);
		return "userDetailForm";
	}
	
	
	@RequestMapping(value="create",method=RequestMethod.POST)
	public String saveUser(@ModelAttribute SystemUserForm systemUserForm, BindingResult result,Model model,HttpServletRequest request) {
		
		SystemUser systemUser = systemUserForm.getSystemUser();

		if (validateSaveUser(systemUserForm, result)) {	
			return JSP_MAIN_FORM;
		}
		
		systemUser.setCreatedBy(getCurrentUserName(request));
		try
		{
			prepareAuditObject(systemUser, request);
			systemUserService.createUser(systemUser);
		}catch(DataStoreException e)
		{
			systemUser.setDateCreated(null); // to show save button
			if(e.isIdentifierConstraint())
				result.rejectValue("systemUser.userName", "error.exist", new String[]{systemUser.getUserName()}, "");
			else if(e.isUniqueConstraint())
			{
				if(StringUtils.equalsIgnoreCase("nric", e.getColumnName()))
					result.rejectValue("systemUser.nric", "error.exist", new String[]{systemUser.getNric()}, "");
				else
					result.reject("error.unknown", new String[]{e.getMessage()}, "");
			}
			else
				result.reject("error.unknown", new String[]{e.getMessage()}, "");
			
			return JSP_MAIN_FORM;
		}
		setSuccessMessage(getMessageResources("success.create","user " + systemUser.getUserName()));
		return "redirect:/user/update/" + systemUser.getUserName();
		

	}
	
	@RequestMapping(value="update/{userName}",method=RequestMethod.POST)
	public String updateUser(@PathVariable String userName,@ModelAttribute SystemUserForm systemUserForm, BindingResult result,Model model,HttpServletRequest request) {

		SystemUser systemUser = systemUserForm.getSystemUser();
		List<SystemRole> systemRoles = systemUser.getSystemRoles();
		request.getSession().setAttribute("systemRoles", systemRoles);
		
		if (validateUpdateUser(systemUserForm, result)) {	
			return JSP_MAIN_FORM;
		}
		
		systemUser.setModifiedBy(getCurrentUserName(request));
		try
		{
			
			prepareAuditObject(systemUser, request);
			systemUserService.updateUser(systemUser);
		}
		catch(DataStoreException e)
		{
			if(e.isIdentifierConstraint())
				result.rejectValue("systemUser.userName", "error.exist", new String[]{systemUser.getUserName()}, "");
			else if(e.isUniqueConstraint())
			{
				if(StringUtils.equalsIgnoreCase("nric", e.getColumnName()))
					result.rejectValue("systemUser.nric", "error.exist", new String[]{systemUser.getNric()}, "");
				else
					result.reject("error.unknown", new String[]{e.getMessage()}, "");
			}
			else
				result.reject("error.unknown", new String[]{e.getMessage()}, "");
			
			return JSP_MAIN_FORM;
		}
		
		setSuccessMessage(getMessageResources("success.update","user " + systemUser.getUserName()));
		return "redirect:/user/update/" + systemUser.getUserName();	
		
	}
	
	@RequestMapping(value={"updateAdHoc/{userName}"}, method = RequestMethod.GET)
	public String detailAdHocUser(@PathVariable String userName,Model model,HttpServletRequest request) {
		detailUser(userName, model, request);
		return JSP_AD_HOC_FORM;
		
	}
	
	@RequestMapping(value="updateAdHoc/{userName}",method=RequestMethod.POST)
	public String updateAdHocUser(@PathVariable String userName,@ModelAttribute SystemUserForm systemUserForm, BindingResult result,Model model,HttpServletRequest request) {

		SystemUser systemUser = systemUserForm.getSystemUser();
		List<SystemRole> systemRoles = systemUser.getSystemRoles();
		request.getSession().setAttribute("systemRoles", systemRoles);
		
		if (validateUpdateUser(systemUserForm, result)) {	
			return JSP_AD_HOC_FORM;
		}
		
		systemUser.setModifiedBy(getCurrentUserName(request));
		try
		{
			
			prepareAuditObject(systemUser, request);
			systemUserService.updateUser(systemUser);
		}
		catch(DataStoreException e)
		{
			if(e.isIdentifierConstraint())
				result.rejectValue("systemUser.userName", "error.exist", new String[]{systemUser.getUserName()}, "");
			else if(e.isUniqueConstraint())
			{
				if(StringUtils.equalsIgnoreCase("nric", e.getColumnName()))
					result.rejectValue("systemUser.nric", "error.exist", new String[]{systemUser.getNric()}, "");
				else
					result.reject("error.unknown", new String[]{e.getMessage()}, "");
			}
			else
				result.reject("error.unknown", new String[]{e.getMessage()}, "");
			
			return JSP_AD_HOC_FORM;
		}
		
		setSuccessMessage(getMessageResources("success.update","user " + systemUser.getUserName()));
		return "redirect:/user/updateAdHoc/" + systemUser.getUserName();	
		
	}
	
	@RequestMapping(value={"update/{userName}","detail/{userName}"}, method = RequestMethod.GET)
	public String detailUser(@PathVariable String userName,Model model,HttpServletRequest request) {
		
		SystemUser systemUser = systemUserService.getUser(userName);
		if(systemUser==null) {
			throw new ResourceNotFoundException("User Id " + userName);
		}
		List<SystemRole> systemRoles = systemUser.getSystemRoles();
		request.getSession().setAttribute("systemRoles", systemRoles);
		SystemUserForm systemUserForm = new SystemUserForm();
		systemUserForm.setSystemUser(systemUser);
		
		model.addAttribute("systemUserForm", systemUserForm);

		return JSP_MAIN_FORM;
		
	}
	
	
	
	@RequestMapping(value={"password/{userName}"}, method = RequestMethod.GET)
	public String detailUserPassword(@PathVariable String userName,Model model,HttpServletRequest request) {
		
		SystemUser systemUser = systemUserService.getUser(userName);
		if(systemUser==null) {
			throw new ResourceNotFoundException("User Id " + userName);
		}
		SystemUserForm systemUserForm = new SystemUserForm();
		systemUserForm.setSystemUser(systemUser);
		systemUser.setPassword("");
		systemUserForm.setFormMode(GlobalConstant.FORM_MODE_UPDATE);
		
		model.addAttribute("systemUserForm", systemUserForm);

		return JSP_USER_PASSWORD;
	}
	
	@RequestMapping(value="password/{userName}",method=RequestMethod.POST)
	public String updateUserPassword(@PathVariable String userName,@ModelAttribute SystemUserForm systemUserForm, BindingResult result,Model model,HttpServletRequest request) {

		SystemUser systemUser = systemUserForm.getSystemUser();

		if (validateUpdateUserPassword(systemUserForm, result)) {	
			return "userDetailPassword";
		}
		systemUser.setModifiedBy(getCurrentUserName(request));
		prepareAuditObject(systemUser, request);
		systemUserService.updateUserPassword(userName,systemUser.getPassword());
		
		setSuccessMessage(getMessageResources("success.update","user password"));
		return "redirect:/user/password/" + systemUser.getUserName();	
		
	}
	
	@RequestMapping(value="role/{userName}", method = RequestMethod.GET)
	public String showUserRole(@PathVariable String userName,Model model) {
		SystemUser systemUser = systemUserService.getUser(userName);
		if(systemUser==null) {
			throw new ResourceNotFoundException("User Id " + userName);
		}
		SystemUserForm systemUserForm = new SystemUserForm();
		systemUserForm.setSystemUser(systemUser);
		systemUserForm.setFormMode(GlobalConstant.FORM_MODE_UPDATE);
		model.addAttribute("systemUserForm", systemUserForm);
		List<SystemRole> systemRoles = systemUser.getSystemRoles();
		if(systemRoles!=null)
		{
			List systemRoleIds = new ArrayList<Integer>();
			for (SystemRole tmp : systemRoles) {
				systemRoleIds.add(tmp.getSystemRoleId());
			}
			
			model.addAttribute("systemRoleIds", JSONArray.fromObject(systemRoleIds));
		}
		else
			model.addAttribute("systemRoleIds", "[]");

		return JSP_USER_ROLE;
	}
	
	@RequestMapping(value="role/{userName}", method = RequestMethod.POST)
	public String updateUserRole(@PathVariable String userName,@ModelAttribute SystemUserForm systemUserForm,Model model,HttpServletRequest request) {
		
		List updatedSystemRoles = new ArrayList<SystemRole>();
		if(systemUserForm.getSystemRoles()!=null)
		{
			List<SystemRole> systemRoles = systemUserForm.getSystemRoles();
			for(int i=0;i<systemRoles.size();i++)
			{
				SystemRole tmp = systemRoles.get(i);
				if(tmp.getSystemRoleId()!=null)
				{
					updatedSystemRoles.add(tmp);
				}
			}
		}
		SystemUser systemUser = systemUserForm.getSystemUser();
		systemUser.setSystemRoles(updatedSystemRoles);
		
		systemUser.setModifiedBy(getCurrentUserName(request));
		prepareAuditObject(systemUser, request);
		systemUserService.updateUserRoles(systemUser);
		
		setSuccessMessage(getMessageResources("success.update","user roles"));
		return "redirect:/user/role/" + userName;
	}

	
	@ModelAttribute(SESSION_SEARCH_CRITERIA)
	public SearchCriteria getUserSearchCriteria()
	{
		return new SearchCriteria();
	}
	

	@RequestMapping(value="search")
	public String searchUser(@ModelAttribute(SESSION_SEARCH_CRITERIA) SearchCriteria searchCriteria,@ModelAttribute("currentSearchCriteria") SearchCriteria currentSearchCriteria,Model model) {
		
		if(currentSearchCriteria.getCurPage()!=null)
			searchCriteria.setCurPage(currentSearchCriteria.getCurPage());
		
		SearchResult searchResult = systemUserService.searchUser(searchCriteria);
		model.addAttribute(searchResult);
		return JSP_MAIN_SEARCH;
	}
	
	@RequestMapping(value={"importPerson"}, method = RequestMethod.POST)
	public @ResponseBody String importPerson(Model model,HttpServletRequest request,HttpServletResponse response) {

		HttpHeaders responseHeaders = new HttpHeaders();
		responseHeaders.add("Content-Type", "text/html");
		
		try
		{
			String filename = request.getHeader("X-File-Name");
			boolean flag = true;
			StringBuilder sb = new StringBuilder();
			
			MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
			Iterator<String> fileNames = multipartRequest.getFileNames();
			while(fileNames.hasNext())
			{
				filename = fileNames.next();
			}
			MultipartFile multipartFile = multipartRequest.getFile(filename);

			InputStream is = multipartFile.getInputStream();

			Workbook workbook = null;
			try
			{
				WorkbookSettings ws = new WorkbookSettings();
				ws.setInitialFileSize(1);
				workbook = Workbook.getWorkbook(is);
				Sheet sheet  = workbook.getSheet(0);

				List<SystemUser> users = new ArrayList<SystemUser>();
				Map<String, Integer> usersMap = new HashMap<String, Integer>();
				sb.append("<ul>");
				
				for(int i=1;i<sheet.getRows();i++){
					boolean inner = true;
					SystemUser user = new SystemUser();
					user.setCreatedBy(getCurrentUserName(request));
					user.setModifiedBy(getCurrentUserName(request));
					user.setDateCreated(new Date());
					user.setDateModified(new Date());
					user.setActive(true);
					user.setUserType(SystemUser.USERTYPE_VISITOR);
					String personName = sheet.getCell(0, i).getContents();
					String nric = sheet.getCell(1, i).getContents();
					String companyName = sheet.getCell(2, i).getContents();
					String unitNo = sheet.getCell(3, i).getContents();
					String contactNo = sheet.getCell(4, i).getContents();
					String mobileNo = sheet.getCell(5, i).getContents();
					String email = sheet.getCell(6, i).getContents();
					String faxNo = sheet.getCell(7, i).getContents();
					String postcode = sheet.getCell(8, i).getContents();
					String address = sheet.getCell(9, i).getContents();
					Department dept = null;
					ContactInfo info = new ContactInfo();
					List<String> message = new ArrayList<String>();
					
					message.add("<li>line ["+i+"] ");
					if(StringUtils.isEmpty(personName)){
						flag = false;
						inner = false;
						message.add("Person Name is empty, ");
					}
					if(StringUtils.isEmpty(nric)){
						flag = false;
						inner = false;
						message.add("NRIC is empty, ");
					}
					if(StringUtils.isNotEmpty(unitNo)){
						dept = getCompanyCode(request, unitNo);
						if(dept == null){
							flag = false;
							inner = false;
							message.add("Company is not exsit, ");
						}
					}else{
						flag = false;
						inner = false;
						message.add("Company Unit No is empty, ");
					}
					user.setPersonName(StringUtils.trim(personName));
					user.setNric(StringUtils.trim(nric));
					user.setUserName(user.getNric());
					user.setDepartment(dept);
					info.setContactNo(StringUtils.trim(contactNo));
					info.setMobileNo(StringUtils.trim(mobileNo));
					info.setFaxNo(StringUtils.trim(faxNo));
					info.setContactAddress(StringUtils.trim(address));
					info.setContactPostalCode(StringUtils.trim(postcode));
					info.setEmail(StringUtils.trim(email));
					user.setContactInfo(info);
					message.add("</li> ");
					if(!inner){
						sb.append(StringUtils.join(message.iterator(), ""));
					}
					
					if(usersMap.get(user.getNric()) != null){
						usersMap.put(user.getNric(), usersMap.get(user.getNric()) + 1);
					}else{
						usersMap.put(user.getNric(), 1);
					}
					
					users.add(user);
				}
				sb.append("</ul>");
				
				if(!vidateImportList(users, usersMap, sb)){
					flag = false;
				}
				
				if(flag){
					for(SystemUser user : users){
						systemUserService.createUser(user);
					}
				}
				
			}catch(Exception e)
			{	
				flag = false;
				sb = new StringBuilder();
				sb.append("<ul><li>import person failed. Please check your excel file.</li></ul>");
				logger.error(CommonUtil.getStackTrace(e));
			}finally{
				if(workbook!=null)
				{
					workbook.close();
				}
			}
			
			Map map = new HashMap();
			map.put("success", "true");

			if(flag){
				map.put("error", "false");
				map.put("result", "<ul><li>import person successfully.</li></ul>");
			}else{
				map.put("error", "true");
				map.put("result", sb.toString());
			}

			StringWriter writer = new StringWriter();
	        ObjectMapper mapper = new ObjectMapper();
	        JsonGenerator jsonGenerator;
			try {
				jsonGenerator = mapper.getJsonFactory().createJsonGenerator(writer);
				mapper.writeValue(jsonGenerator, map);
				return writer.toString();
			} catch (IOException e) {
				logger.error(CommonUtil.getStackTrace(e));
			}
	
		} catch (Exception e) {
			
			logger.error(CommonUtil.getStackTrace(e));
		}
		
		return "[]";
	}
	
	
	@RequestMapping(value={"exportPerson"}, method = RequestMethod.GET)
	public void printAssetBalance(HttpServletRequest request,HttpServletResponse response) throws IOException {
		try
		{
			SearchCriteria searchCriteria = new SearchCriteria();
			searchCriteria.addSearchCriteria("active", true);
			searchCriteria.addSearchCriteria("userType", SystemUser.USERTYPE_VISITOR);
			searchCriteria.setCurPage(Integer.MAX_VALUE);
			searchCriteria.setMaxRow(Integer.MAX_VALUE);
			SearchResult tmp = systemUserService.searchUser(searchCriteria);
	
			ServletOutputStream servletOutputStream = response.getOutputStream();	
			InputStream reportStream = null;
			
			reportStream =getClass().getClassLoader().getResourceAsStream("report/Person.jasper");	
			JasperPrint jasperPrint = JasperFillManager.fillReport(reportStream, null, new JRBeanCollectionDataSource(tmp.getResult()));
			JExcelApiExporter exporterXLS = new JExcelApiExporter();
			exporterXLS.setParameter(JRXlsExporterParameter.JASPER_PRINT, jasperPrint);
			exporterXLS.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);
			exporterXLS.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
			exporterXLS.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
			exporterXLS.setParameter(JRXlsExporterParameter.OUTPUT_STREAM, servletOutputStream );
			response.setHeader("Content-Disposition", "attachment; filename=Person.xls");
			response.setContentType("application/vnd.ms-excel");
			exporterXLS.exportReport();
			servletOutputStream.flush();
			servletOutputStream.close();
		}
		catch (JRException e)
		{
			logger.error(CommonUtil.getStackTrace(e));
			// display stack trace in the browser
			StringWriter stringWriter = new StringWriter();
			PrintWriter printWriter = new PrintWriter(stringWriter);
			e.printStackTrace(printWriter);
			response.setContentType("text/plain");
			response.getOutputStream().print(stringWriter.toString());
		}

	}
	
	
	private boolean validateUpdateUserPassword(SystemUserForm systemUserForm,BindingResult bindingResult)
	{
		SystemUser systemUser = systemUserForm.getSystemUser();
		
		if(!StringUtils.equalsIgnoreCase(systemUser.getPassword(),systemUserForm.getConfirmPassword()))
			bindingResult.rejectValue("confirmPassword", "error.password.notsame", null, "");
		
		return bindingResult.hasErrors();
	}
	
	
	private boolean validateSaveUser(SystemUserForm systemUserForm,BindingResult result)
	{
		SystemUser systemUser = systemUserForm.getSystemUser();
		
		validator.validate(systemUserForm, result);

		if(!StringUtils.equalsIgnoreCase(systemUser.getPassword(),systemUserForm.getConfirmPassword()))
			result.rejectValue("confirmPassword", "error.password.notsame", null, "");	
		

		SearchCriteria searchCriteria = new SearchCriteria();
		searchCriteria.addSearchCriteria("userName", systemUser.getUserName());
		SystemUser tmp = systemUserService.findUser(searchCriteria);
		if(tmp!=null)
			result.rejectValue("systemUser.userName", "error.exist", new String[]{systemUser.getUserName()}, "");
		
		return result.hasErrors();
	}
	
	private boolean validateUpdateUser(SystemUserForm systemUserForm,BindingResult result)
	{
		validator.validate(systemUserForm, result);

		return result.hasErrors();
	}
	
	private void prepareAuditObject(SystemUser systemUser, HttpServletRequest request){
		/* audit start */
		Department department = new Department();
		Section section = new Section();
		
	
		
		if(systemUser.getDepartment()!=null)
		 department = findDepartment(request, systemUser.getDepartment().getDepartmentId());
		
	
		if(systemUser.getSection()!=null)
		 section = findSection(request, systemUser.getSection().getSectionId());
				
		systemUser.setDepartment(department);
		systemUser.setSection(section);
		
		List<SystemRole> list = systemUser.getSystemRoles();
		if(CollectionUtils.isNotEmpty(list)){

			for(SystemRole systemRole : list){
				if(systemRole.getSystemRoleId() != null){
					SystemRole tmp = findSystemRole(request, systemRole.getSystemRoleId());
					systemRole.setRoleName(tmp.getRoleName());
					
				}
			}
		}
		
				
		/* audit end */	
	}
	
	
	private Department getCompanyCode(HttpServletRequest request, String companyUnitNo)
	{
		List<Department> depts = (List<Department> )request.getSession().getServletContext().getAttribute(GlobalConstant.GLOBAL_DEPARTMENT);
		for(Department dept : depts){
			if(StringUtils.equals(dept.getDepartmentUnitNo(), companyUnitNo)){
				return dept;
			}
		}
		return null;
	}
	
	
	private boolean vidateImportList(List<SystemUser> list, Map<String, Integer> maps, StringBuilder sb)
	{
		boolean flag = true;
		sb.append("<ul>");
		if(maps != null){
			for(Map.Entry<String, Integer> entry : maps.entrySet()){
				if(entry.getValue() > 1){
					flag = false;
					sb.append("<li> There are "+ entry.getValue() +" persons have the same NRIC "+entry.getKey()+" in the excel file. </li>");
				}
			}
		}
		if(list != null){
			for(int i=0; i<list.size(); i++){
				SystemUser user = list.get(i);
				if(!validateImportNRIC(user.getNric())){
					flag = false;
					sb.append("<li> line ["+(i+1)+"] the system has already haven the same NRIC ["+user.getNric()+"] in the Person Table </li>");
				}
			}
		}
		sb.append("</ul>");
		return flag;
	}
	
	
	private boolean validateImportNRIC(String nric)
	{
		SearchCriteria searchCriteria = new SearchCriteria();
		searchCriteria.addSearchCriteria("nric", nric);
		SystemUser tmp = systemUserService.findUser(searchCriteria);
		if(tmp!=null)
			return false;
		return true;
	}
}
